Sharing State Between Components in React JS
Sharing State Between Components is an important concept in React JS that allows multiple components to access and work with the same changing data. When two or more components need to display or update related information, React developers can move the shared state to their closest common parent component and pass the data and event handlers to child components through props.
This technique is called Lifting State Up. It helps keep components synchronized, avoids duplicate state, and makes applications easier to maintain.
React JS Training Course: JustAcademy React JS Training Course
Course Demo: Register for React JS Course Demo
1. What Is State in React?
State is data that a React component remembers between renders. It can change because of user interactions, application events, or other updates. When state changes, React can render the component again to display the updated information.
The useState() Hook is commonly used to add state to functional components.
Syntax:
import { useState } from "react";
const [value, setValue] = useState(initialValue);
- value: The current state value.
- setValue: The function used to request a state update.
- initialValue: The initial state value.
Example:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
}
export default Counter;
In this example, the count state belongs to the Counter component. Clicking the button requests a state update, and React displays the new count.
2. What Is Sharing State Between Components?
Sharing state between components means allowing multiple components to use the same source of changing data. Instead of maintaining separate copies of the same information, one component owns the shared state and passes the necessary values to other components.
For example, an online shopping application may have a ProductList component and a CartSummary component. When a user adds a product to the cart, both components may need to reflect the updated cart information.
Example Structure:
App
├── ProductList
└── CartSummary
The App component can store the cart state and pass the cart data to ProductList and CartSummary using props.
Common Use Cases:
- Sharing a shopping cart between product and checkout components.
- Synchronizing multiple form inputs.
- Displaying the same user profile information in different components.
- Sharing search text between a search bar and filtered results.
- Keeping multiple accordion panels coordinated.
- Sharing selected filters between a filter panel and product list.
- Updating a dashboard when its underlying data changes.
3. Why Is Sharing State Important?
When multiple components depend on the same information, separate state variables can become inconsistent. Sharing a single source of truth helps components display consistent information.
| Benefit |
Explanation |
| Consistency |
Components receive data from the same state source. |
| Less duplication |
The same information does not need to be maintained independently in several components. |
| Better coordination |
A change made through one component can update other components that depend on the shared state. |
| Maintainability |
State ownership and update logic are easier to understand. |
| Reusable components |
Child components can receive values and callbacks through props instead of managing all shared logic themselves. |
| Predictable data flow |
Data flows from parent to child, while callbacks allow children to request updates. |
4. What Is Lifting State Up?
Lifting State Up is the process of moving state from individual child components to their closest common parent when multiple components need to coordinate around the same data.
After moving the state to the parent, the parent passes state values and event-handler functions to its children through props.
Before Lifting State Up:
App
├── ComponentA (own state)
└── ComponentB (own state)
Both components manage their own state independently. Updating one does not automatically update the other.
After Lifting State Up:
App (shared state)
├── ComponentA (receives props)
└── ComponentB (receives props)
The parent manages the shared state and provides the data needed by both components.
Steps to Lift State Up:
- Identify the components that need to share information.
- Find their closest common parent component.
- Move the shared state into that parent.
- Pass the state value to child components through props.
- Pass event-handler functions to children so they can request state updates.
- Render the updated information using the shared state.
5. Sharing State Using Props
Props are values passed from a parent component to a child component. They can contain strings, numbers, arrays, objects, functions, and other data.
When sharing state, the parent passes its state value through a prop. The child reads the value and displays it.
Example:
function Parent() {
const message = "Welcome to React";
return <Child message={message} />;
}
function Child({ message }) {
return <h2>{message}</h2>;
}
export default Parent;
Output: Welcome to React
Here, Parent passes the message to Child. In this example, the message is a regular variable rather than state, but the same prop-passing approach works with a state value.
Passing State as a Prop:
import { useState } from "react";
function Parent() {
const [name, setName] = useState("Amit");
return <Child name={name} />;
}
function Child({ name }) {
return <p>User: {name}</p>;
}
export default Parent;
Whenever the parent updates the name state, the child receives the latest name prop and renders accordingly.
6. Passing Event Handlers to Child Components
Props are read-only from the child's perspective. A child should not directly modify a parent's state variable. Instead, the parent can pass a callback function that the child calls when an event occurs.
Example:
import { useState } from "react";
function Parent() {
const [count, setCount] = useState(0);
function increaseCount() {
setCount(previousCount => previousCount + 1);
}
return (
<div>
<h2>Count: {count}</h2>
<Child onIncrease={increaseCount} />
</div>
);
}
function Child({ onIncrease }) {
return (
<button onClick={onIncrease}>
Increase Count
</button>
);
}
export default Parent;
How it works:
- Parent stores the count state.
- Parent passes the increaseCount function to Child as the onIncrease prop.
- Child calls the callback when the button is clicked.
- The callback updates the state owned by Parent.
- React renders Parent again with the updated count.
This is a common pattern for sharing state and coordinating interactions between components.
7. Complete Example: Two Components Sharing a Counter
In this example, two child components display the same count and can both increase it. Because they use the state owned by their common parent, the displayed values remain synchronized.
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
function increaseCount() {
setCount(previousCount => previousCount + 1);
}
return (
<div>
<h1>Shared Counter</h1>
<CounterDisplay count={count} />
<CounterControls onIncrease={increaseCount} />
</div>
);
}
function CounterDisplay({ count }) {
return <h2>Current Count: {count}</h2>;
}
function CounterControls({ onIncrease }) {
return (
<button onClick={onIncrease}>
Increase Shared Count
</button>
);
}
export default App;
Explanation: The App component owns the count state. CounterDisplay receives the count value, while CounterControls receives the callback that updates it. After a click, both components receive props based on the updated state.
8. Controlled and Uncontrolled Components
Controlled and uncontrolled describe how a component's important information is managed.
| Feature |
Controlled Component |
Uncontrolled Component |
| Data source |
Important values are driven by props from a parent. |
Important values are managed primarily by the component's own state or internal behavior. |
| Parent control |
The parent can determine the value through props. |
The parent has less direct control over the internal value. |
| Coordination |
Useful when multiple components must stay synchronized. |
Useful when a component can work independently. |
| Example |
An input whose value comes from a parent prop. |
An input whose value is managed by the browser using a ref or defaultValue. |
Controlled Input Example:
function NameInput({ name, onNameChange }) {
return (
<input
value={name}
onChange={(event) => onNameChange(event.target.value)}
/>
);
}
The parent supplies the current name and the callback that handles changes. The input displays the value supplied through props.
9. Practical Example: Sharing State Between Two Inputs
Suppose two input fields should always display the same text. Instead of giving each input a separate state variable, store the text in their common parent.
import { useState } from "react";
function App() {
const [text, setText] = useState("");
return (
<div>
<h2>Synchronized Inputs</h2>
<TextInput
label="First Input"
value={text}
onValueChange={setText}
/>
<TextInput
label="Second Input"
value={text}
onValueChange={setText}
/>
<p>Shared Text: {text}</p>
</div>
);
}
function TextInput({ label, value, onValueChange }) {
return (
<label>
{label}
<input
value={value}
onChange={(event) => onValueChange(event.target.value)}
/>
</label>
);
}
export default App;
Result: Typing in either input updates the shared state, so both inputs display the same text.
Key lesson: Both inputs use one state variable owned by App. The callback updates that shared state, and the updated value flows back down through props.
10. Practical Example: Sharing State in an Accordion
An accordion contains multiple panels. If only one panel should be open at a time, the parent can store the index of the active panel and pass each panel its active status and an event handler.
import { useState } from "react";
function Accordion() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<div>
<Panel
title="React"
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
React is a JavaScript library for building user interfaces.
</Panel>
<Panel
title="State"
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
State stores information that can change over time.
</Panel>
</div>
);
}
function Panel({ title, isActive, onShow, children }) {
return (
<section>
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={onShow}>Show</button>
)}
</section>
);
}
export default Accordion;
How it works:
- The Accordion component stores the active panel index.
- Each Panel receives an isActive prop.
- Each Panel receives an onShow callback.
- Clicking Show updates the parent's activeIndex state.
- Only the panel whose index matches activeIndex displays its content.
11. Sharing State Between Components at Different Levels
Sometimes, the components that need the same data are nested deeply in the component tree. One option is to lift the state to a common ancestor and pass it through props. However, passing props through many intermediate components that do not use them can become inconvenient.
Example Component Tree:
App (shared state)
└── Dashboard
└── MainSection
└── UserProfile (needs shared state)
If several intermediate components must pass the same data without using it, this pattern is often called prop drilling.
For deeply nested data that many components need, React Context may be a useful alternative. Context lets a parent provide information to components below it without passing the same prop through every intermediate level.
12. Sharing State with React Context
React Context is useful when multiple components need access to the same information across different levels of the component tree. Common examples include the current theme, authenticated user information, or locale settings.
Example:
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext(null);
function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemeButton />;
}
function ThemeButton() {
const context = useContext(ThemeContext);
if (context === null) {
throw new Error("ThemeButton must be inside ThemeContext.Provider");
}
const { theme, setTheme } = context;
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Current theme: {theme}
</button>
);
}
export default App;
In this example, App owns the theme state and provides both the current theme and its setter through Context. ThemeButton can access that information even though Toolbar sits between App and ThemeButton.
Note: Context is not necessary for every shared state problem. For a small number of closely related components, lifting state up and passing props is often simpler.
13. Props vs State
| Feature |
Props |
State |
| Meaning |
Data passed into a component. |
Data managed by a component or a state-owning parent. |
| Who provides it? |
Usually the parent component. |
The component that declares and owns the state. |
| Can the receiving component assign to it directly? |
No. Props should be treated as read-only. |
No. Update state through its setter or appropriate state-management mechanism. |
| Purpose in sharing state |
Pass shared values and callbacks to child components. |
Store the changing data in one appropriate location. |
| Example |
|
const [count, setCount] = useState(0) |
14. Common Mistakes When Sharing State
- Maintaining duplicate state unnecessarily: Separate copies of the same changing information can become inconsistent.
- Placing shared state in the wrong component: State should usually live in the closest common parent that needs to coordinate the components.
- Trying to change props directly: Child components should request changes through callbacks instead of modifying props.
- Forgetting to pass a callback: A child cannot update parent-owned state unless the parent provides an appropriate function or another state-management mechanism.
- Passing the wrong value: Ensure each component receives the correct state value and handler.
- Overusing Context: Simple parent-child sharing usually works well with props.
- Storing derived values unnecessarily: If a value can be calculated from existing props or state, consider calculating it instead of maintaining a duplicate state variable.
- Updating state based on a stale value: When the next value depends on the previous one, use a functional updater such as
setCount(previous => previous + 1).
15. Best Practices for Sharing State
- Choose one clear owner for each piece of shared state.
- Lift state to the closest common parent of the components that need it.
- Pass data down through props.
- Pass callbacks down when child interactions need to update parent-owned state.
- Keep component responsibilities clear and focused.
- Use functional state updates when the next value depends on the previous value.
- Avoid unnecessary duplicate or derived state.
- Use Context when information needs to be accessed by many descendants or prop passing becomes cumbersome.
- Consider a dedicated state-management library only when the application's complexity justifies it.
- Use React Developer Tools to inspect component props and state when debugging.
16. Sharing State vs Lifting State Up vs Context
| Approach |
How It Works |
Best Used When |
| Props |
A parent passes values and callbacks directly to children. |
Components are closely related in the component tree. |
| Lifting State Up |
Shared state moves to the closest common parent. |
Sibling components must stay synchronized. |
| React Context |
A provider makes values available to descendant components. |
Many descendants need shared information across several levels. |
| State-management library |
Shared state is managed using an external state-management solution. |
The application has complex state requirements that benefit from centralized or specialized management. |
17. Interview Questions and Answers
Q1. What is sharing state between components?
Answer: It means allowing multiple components to use the same changing data, usually by keeping the state in a common parent and passing it down through props.
Q2. What is lifting state up?
Answer: Lifting state up means moving state from child components to their closest common parent so the components can coordinate using the same data.
Q3. Why is lifting state up useful?
Answer: It avoids duplicate state, helps components stay synchronized, and provides a clear source of truth.
Q4. How can a child component update parent-owned state?
Answer: The parent passes a callback function to the child through props. The child calls the callback when an interaction occurs.
Q5. Can a child component modify props directly?
Answer: No. Props should be treated as read-only. A child can request a change by calling a callback supplied by its parent.
Q6. What is a controlled component?
Answer: A controlled component has important values driven by props, allowing its parent to determine those values.
Q7. What is prop drilling?
Answer: Prop drilling is passing props through intermediate components that do not need the data themselves, simply so a deeper component can receive it.
Q8. How can React Context help?
Answer: Context allows a parent to provide information to descendant components without explicitly passing the same prop through every intermediate component.
Q9. Should all state be placed in the top-level App component?
Answer: No. Each piece of state should live in a component appropriate to its use. Shared state can be lifted to the closest common parent, while independent state can remain local.
Q10. What does single source of truth mean?
Answer: For a particular piece of shared information, one component is responsible for owning and updating the state, while other components use the values it provides.
18. Practice Exercises
- Create two counter components that display and update the same count.
- Build two synchronized text inputs that share a single state variable.
- Create a shopping cart where ProductList and CartSummary display information from shared state.
- Build an accordion in which only one panel can be expanded at a time.
- Create a parent component that passes a username and an update callback to a child component.
- Build a search box and a results component that share the search query.
- Create a theme switcher using React Context.
- Refactor two components that maintain duplicate data so they use one shared state source.
19. Quick Revision Checklist
- Understand state and the
useState() Hook.
- Know why multiple components may need shared state.
- Understand lifting state up.
- Identify the closest common parent of related components.
- Pass state values through props.
- Pass callback functions to child components.
- Understand controlled and uncontrolled components.
- Recognize prop drilling and know when Context may help.
- Avoid unnecessary duplicate and derived state.
- Practice building synchronized components.
20. Useful Learning Resources
Conclusion
Sharing State Between Components is a fundamental React JS concept for building synchronized and maintainable user interfaces. The most common approach is to lift shared state to the closest common parent, pass data down through props, and provide callback functions so child components can request updates. For data needed by many deeply nested components, React Context can reduce repetitive prop passing.
Practice these concepts by building synchronized inputs, shared counters, accordions, search interfaces, and shopping carts. To learn more, visit the JustAcademy React JS Training Course or register for a course demo.